/*!
\copyright  Copyright (c) 2022-2023 Qualcomm Technologies International, Ltd.
            All Rights Reserved.
            Qualcomm Technologies International, Ltd. Confidential and Proprietary.
\file
\brief      Implementation of le audio unicast client functionality.
*/

#ifdef INCLUDE_LE_AUDIO_UNICAST_SOURCE
#include "le_audio_client_context.h"
#include "le_audio_client_messages.h"
#include "tmap_client_source_unicast.h"

#include "logging.h"
#include "tmap_server_role.h"
#include <kymera.h>
#include "volume_messages.h"

#include <feature.h>
#ifdef INCLUDE_QCOM_CON_MANAGER
#include <qualcomm_connection_manager.h>
#endif

#define LC3_EPC_FEATURE_ID                                      (LC3_EPC_HEADSET)

#define LE_AUDIO_CLIENT_CLIENT_MICROPHONE_COUNT                 (CAP_PROFILE_CLIENT_MICROPHONE_COUNT_ONE)

#define LE_AUDIO_CLIENT_DEFAULT_CONFIG_MODE                      (CAP_CLIENT_CIG_CONFIG_MODE_DEFAULT | CAP_CLIENT_MODE_JOINT_STEREO)
#define LE_AUDIO_CLIENT_CONFIG_MODE_QHS                          (LE_AUDIO_CLIENT_DEFAULT_CONFIG_MODE | CAP_CLIENT_CIG_CONFIG_MODE_QHS)

#define LeAudioClient_IsContextOfTypeGaming(context) (context == CAP_CLIENT_CONTEXT_TYPE_GAME || \
                                                      context == CAP_CLIENT_CONTEXT_TYPE_GAME_WITH_VBC)

#define LeAudioClient_IsContextOfTypeGamingWithoutVBC(context) (context == CAP_CLIENT_CONTEXT_TYPE_GAME)

#define LeAudioClient_SnkAptxLite48kSupported(group_handle) \
    (bool) CapProfileClient_IsStreamCapabilitySupported(group_handle, CAP_CLIENT_STREAM_CAPABILITY_APTX_LITE_48_1)

#define LeAudioClient_SrcAptxLite16kSupported(group_handle) \
    (bool) CapProfileClient_IsStreamCapabilitySupported(group_handle, CAP_CLIENT_STREAM_CAPABILITY_APTX_LITE_16_1)


#ifdef INCLUDE_LE_AUDIO_GAMING_MODE_APTX_LITE
static bool leAudioClient_CanAptxLiteCodecBeUsed(uint16 audio_context, ServiceHandle group_handle)
{
    return (LeAudioClient_IsContextOfTypeGaming(audio_context) && LeAudioClient_SnkAptxLite48kSupported(group_handle) &&
           (LeAudioClient_IsContextOfTypeGamingWithoutVBC(audio_context) || LeAudioClient_SrcAptxLite16kSupported(group_handle)));
}

static void leAudioClient_UpdateFlushTimeoutRange(ServiceHandle group_handle, cap_profile_ft_info_t * cap_ft_info)
{
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    DEBUG_LOG("leAudioClient_UpdateFlushTimeoutRange");

    if (leAudioClient_CanAptxLiteCodecBeUsed(client_ctxt->configured_audio_contexts, group_handle))
    {
        QCOM_CON_MANAGER_SET_FLUSH_TIMEOUT_RANGE_PARAM_T ft_info;

        ft_info.cig_id = CapProfileClient_GetCigId(group_handle);
        ft_info.enable = QCOM_CON_MANAGER_SET_FLUSH_TIMEOUT_ENABLE;
        ft_info.ll_mode = cap_ft_info->latency_mode;
        ft_info.min_ft_c_to_p = cap_ft_info->min_flush_timeout;
        ft_info.max_ft_c_to_p = cap_ft_info->max_flush_timeout;
        ft_info.min_ft_p_to_c = LE_AUDIO_FLUSH_TIMEOUT_MIN_P_TO_C_DEFAULT;
        ft_info.max_ft_p_to_c = LE_AUDIO_FLUSH_TIMEOUT_MAX_P_TO_C_DEFAULT;
        ft_info.ttp_adjust_rate = LE_AUDIO_FLUSH_TIMEOUT_TTP_ADJUST_RATE_DEFAULT;
        
        QcomConManagerSetFlushTimeoutRange(&ft_info);
    }
}

static void leAudioClient_SetDefaultFlushTimeoutRange(ServiceHandle group_handle, uint16 audio_context)
{
    DEBUG_LOG("leAudioClient_SetDefaultFlushTimeoutRange audio_context:0x%x", audio_context);

    if (leAudioClient_CanAptxLiteCodecBeUsed(audio_context, group_handle))
    {
        cap_profile_ft_info_t ft_info;

        ft_info.min_flush_timeout = LE_AUDIO_FLUSH_TIMEOUT_MIN_C_TO_P_DEFAULT;
        ft_info.max_flush_timeout = LE_AUDIO_FLUSH_TIMEOUT_MAX_C_TO_P_DEFAULT;
        ft_info.max_bit_rate = 0;
        ft_info.latency_mode = QCOM_CON_SET_FLUSH_LL_MODE_NO_CHANGE;
        ft_info.err_resilience = 0;

        leAudioClient_UpdateFlushTimeoutRange(group_handle, &ft_info);
    }
}

#else
#define leAudioClient_CanAptxLiteCodecBeUsed(audio_context, group_handle) (UNUSED(audio_context), UNUSED(group_handle), FALSE)
#define leAudioClient_UpdateFlushTimeoutRange(group_handle, cap_ft_info) (UNUSED(group_handle), UNUSED(cap_ft_info))
#define leAudioClient_SetDefaultFlushTimeoutRange(group_handle, audio_context) (UNUSED(group_handle), UNUSED(audio_context))

#endif /* INCLUDE_LE_AUDIO_GAMING_MODE_APTX_LITE */

/* Process the cancel start stream request */
static void leAudioClient_HandleCancelStartStreamRequest(ServiceHandle group_handle, uint16 audio_context_configured, bool config_success)
{
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    if (config_success)
    {
        /* Remove the configured audio contexts before sending cancelled start stream indication to clients. */
        if (leAudioClient_GetProfileForAudioContext(audio_context_configured) == LE_AUDIO_CLIENT_PROFILE_CAP)
        {
            CapProfileClient_RemoveGamingConfiguration(group_handle, audio_context_configured);
        }
        else
        {
            TmapClientSource_RemoveConfiguration(group_handle, audio_context_configured);
        }
    }
    else
    {
        /* No configured audio context. Send cancelled start stream indcation to all registered clients */
        leAudioClientMessages_SendStreamStartCancelCompleteInd(group_handle, TRUE);
        leAudioClient_EndUnicastAudioSession(&client_ctxt->session_data);
    }
}

/* Codec & QoS configuration completed.Start the unicast streaming */
static void leAudioClient_StartStreamingIfConfigCompleted(ServiceHandle group_handle, uint16 audio_context_configured, bool config_success)
{
    CapClientContext requested_audio_context;
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    DEBUG_LOG("leAudioClient_StartStreamingIfConfigCompleted audio_context:0x%x success:%d", audio_context_configured, config_success);

    if (client_ctxt->requested_audio_contexts == 0 ||
        !config_success)
    {
        /* Either requested contexts have been cleared (ie, cancel start stream request have received) or
           the configuration failed. Send a Stream start failure indication to registered clients */
        leAudioClientMessages_SendUnicastStreamStartInd(group_handle, FALSE, audio_context_configured);

        if (client_ctxt->requested_audio_contexts == 0)
        {
            /* Handles the received cancel start stream request. */
            leAudioClient_HandleCancelStartStreamRequest(group_handle, audio_context_configured, config_success);
            return;
        }

        /* Reset the session data */
        leAudioClient_EndUnicastAudioSession(&client_ctxt->session_data);
    }
    else
    {
        /* The requested audio context have configured successfully and there is no cancel start stream command
           received in between. Proceed to start streaming after updating the configured audio contexts */
        client_ctxt->configured_audio_contexts |= audio_context_configured;

         /* Start the unicast streaming for the audio context */
        if (leAudioClient_GetProfileForAudioContext(audio_context_configured) == LE_AUDIO_CLIENT_PROFILE_CAP)
        {
            leAudioClient_SetDefaultFlushTimeoutRange(group_handle, audio_context_configured);
            CapProfileClient_StartUnicastStreaming(group_handle, audio_context_configured);
        }
        else
        {
            TmapClientSource_StartUnicastStreaming(group_handle, audio_context_configured);
        }
    }

    /* Clear the audio context from requested contexts list */
    requested_audio_context = leAudioClient_GetNextAudioContextToConfigure(client_ctxt->requested_audio_contexts);
    client_ctxt->requested_audio_contexts &= (~requested_audio_context);
}

#ifdef INCLUDE_QCOM_CON_MANAGER
static void leAudioClient_EnableWBM(void)
{
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    if (FeatureVerifyLicense(LC3_EPC_FEATURE_ID) && client_ctxt->session_data.codec_qos_config.codecVersionNum == 1)
    {
        le_audio_client_cis_devices_info_t *dev, *end_dev;
        le_audio_client_cis_info_t *cis, *end_cis;

        for (dev = &client_ctxt->session_data.devices_cis_info[0],
                end_dev = &dev[LE_AUDIO_CLIENT_MAX_DEVICES_SUPPORTED - 1]; dev <= end_dev; dev++)
        {
            if (dev->cid != INVALID_CID)
            {
                for (cis = &dev->cis_info[0], end_cis = &dev->cis_info[dev->cis_count]; cis < end_cis; cis++)
                {
                    DEBUG_LOG("leAudioClient_EnableWBM Enabling WBM for CIS handle 0x%x", cis->cis_handle);
                    QcomConManagerSetWBMFeature(cis->cis_handle, TRUE);
                }
            }
        }
    }
}
#endif

static bool leAudioClient_IsCisInfoAvailableForSession(void)
{
    bool status = FALSE;
    le_audio_client_cis_devices_info_t *dev, *end_dev;

    for (dev = &leAudioClient_GetContext()->session_data.devices_cis_info[0],
            end_dev = &dev[LE_AUDIO_CLIENT_MAX_DEVICES_SUPPORTED - 1]; dev <= end_dev; dev++)
    {
        if (dev->cid != INVALID_CID)
        {
            if (dev->cis_count == 0)
            {
                status = FALSE;
                break;
            }

            status = TRUE;
        }
    }

    DEBUG_LOG("leAudioClient_IsCisInfoAvailableForSession %d", status);

    return status;
}

/* If Unicast audio streaming started, inform the registered clients */
static void leAudioClient_HandleUnicastStreamStartInd(bool stream_started)
{
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    DEBUG_LOG("leAudioClient_HandleUnicastStreamStartInd Streaming Start success: %d", stream_started);

#ifdef INCLUDE_QCOM_CON_MANAGER
    leAudioClient_EnableWBM();
#endif

    leAudioClientMessages_SendUnicastStreamStartInd(client_ctxt->group_handle,
                                                    stream_started,
                                                    client_ctxt->session_data.audio_context);

    if (stream_started && leAudioClient_IsCisInfoAvailableForSession())
    {
        leAudioClient_SetStreamingState();
    }
    else
    {
        /* Cleanup the session data */
        leAudioClient_EndUnicastAudioSession(&client_ctxt->session_data);
    }
}

void leAudioClient_HandleUnicastVolumeStateInd(uint8 volume_state)
{
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    DEBUG_LOG("leAudioClient_HandleUnicastVolumeStateInd Le Audio state %d, Volume state: %d", LeAudioClient_GetState(), volume_state);

    if (leAudioClient_IsInConnectedState())
    {
        if (client_ctxt->session_data.audio_context == CAP_CLIENT_CONTEXT_TYPE_CONVERSATIONAL)
        {
            Volume_SendVoiceSourceVolumeUpdateRequest(voice_source_le_audio_unicast, event_origin_external, volume_state);
        }
        else
        {
            /* If session context is not yet set also (it can happen if in connected state), consider it as audio */
            Volume_SendAudioSourceVolumeUpdateRequest(audio_source_le_audio_unicast, event_origin_external, volume_state);
        }
    }
}

/* CIS Isochronous streams have got connected for a audio session Store them in context */
static void leAudioClient_HandleUnicastCisConnectInd(gatt_cid_t cid, uint8 cis_count, void *cis_handles, void *codec_qos_config,
                                                     bool cis_connect_status)
{
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();
    CapClientCisHandles *cis_info = (CapClientCisHandles *) cis_handles;

    DEBUG_LOG("leAudioClient_HandleUnicastCisConnectInd CisCount : %d, success : %d", cis_count, cis_connect_status);

    if (cis_connect_status)
    {
        le_audio_client_cis_devices_info_t *dev, *end_dev;
        le_audio_client_cis_info_t *cis, *end_cis;

        client_ctxt->session_data.codec_qos_config = *(CapClientAudioConfig *) codec_qos_config;

        DEBUG_LOG("leAudioClient_HandleUnicastCisConnectInd vendorCodecId : 0x%x, codecId : 0x%x",
                  client_ctxt->session_data.codec_qos_config.vendorCodecId, client_ctxt->session_data.codec_qos_config.codecId);

        for (dev = &client_ctxt->session_data.devices_cis_info[0],
                end_dev = &dev[LE_AUDIO_CLIENT_MAX_DEVICES_SUPPORTED - 1]; dev <= end_dev; dev++)
        {
            /* See if any free slot to populate the received cis info */
            if (dev->cid == INVALID_CID)
            {
                /* Populate the cid and cis count */
                dev->cid = cid;
                dev->cis_count = cis_count;

                PanicFalse(cis_count <= LE_AUDIO_CLIENT_MAX_CIS_PER_DEVICE);

                for (cis = &dev->cis_info[0], end_cis = &dev->cis_info[dev->cis_count]; cis < end_cis; cis++)
                {
                    if (cis_info->direction == LE_CIS_DIRECTION_BOTH)
                    {
                        client_ctxt->session_data.enable_vbc = TRUE;
                    }

                    cis->cis_handle = cis_info->cisHandle;
                    cis->audio_location = cis_info->audioLocation;
                    cis->direction = cis_info->direction;
                    cis_info++;
                }
                break;
            }
        }
    }
}

/* Inform registered clients with a stream stopped indication */
static void leAudioClient_HandleUnicastStreamStopInd(ServiceHandle group_handle, le_audio_client_profiles_t profile, bool stream_stopped)
{
    bool stop_streaming = FALSE;
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    DEBUG_LOG("leAudioClient_HandleUnicastStreamStopInd success:%d", stream_stopped);

    if (client_ctxt->session_data.release_config)
    {
        if (leAudioClient_IsInUnicastStreaming())
        {
            stop_streaming = profile == LE_AUDIO_CLIENT_PROFILE_TMAP ? TmapClientSource_StopUnicastStreaming(group_handle, TRUE) :
                                                                       CapProfileClient_StopUnicastStreaming(group_handle, TRUE);
            if (stop_streaming)
            {
                client_ctxt->session_data.release_config = FALSE;
            }
        }
    }
    else
    {
        leAudioClientMessages_SendUnicastStreamStopInd(group_handle, stream_stopped,
                                                        client_ctxt->session_data.audio_context);
        /* Cleanup the session data */
        leAudioClient_EndUnicastAudioSession(&client_ctxt->session_data);
        leAudioClient_ClearStreamingState();
    }
}

/* Inform registered clients with a stream start cancelled indication */
static void leAudioClient_HandleUnicastConfigRemovedInd(ServiceHandle group_handle, bool config_remove_success)
{
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    DEBUG_LOG("leAudioClient_HandleUnicastConfigRemovedInd success:%d", config_remove_success);

    /* Send indication that stream request cancelled successfully */
    leAudioClientMessages_SendStreamStartCancelCompleteInd(group_handle, TRUE);
    /* Cleanup the session data */
    leAudioClient_EndUnicastAudioSession(&client_ctxt->session_data);
}

/* Inform registered clients with a stream stopped indication */
static void leAudioClient_HandleCisLinkLossInd(const CAP_PROFILE_CLIENT_CIS_LINK_LOSS_T *message)
{
    DEBUG_LOG("leAudioClient_HandleCisLinkLossInd 0x%04X", message->group_handle);
    /* ToDo: CIS link loss */
}

static CapClientSreamCapability leAudioClient_GetStreamCapability(uint32 stream_capability)
{
    if (stream_capability != CAP_CLIENT_STREAM_CAPABILITY_UNKNOWN)
    {
        /* Enable LC3 EPC when license is available and on valid stream */
        stream_capability |= FeatureVerifyLicense(LC3_EPC_FEATURE_ID) ? CAP_CLIENT_STREAM_CAPABILITY_LC3_EPC : 0;
    }

    DEBUG_LOG("leAudioClient_GetStreamCapability stream_capability 0x%x", stream_capability);

    return stream_capability;
}

/* Handles the messages from CAP profile while in PTS mode */
static void leAudioClient_ProcessCapUnicastMessageInPtsMode(const cap_profile_client_msg_t *ind)
{
    switch (ind->id)
    {
        case CAP_PROFILE_CLIENT_MSG_ID_UNICAST_CONFIG_COMPLETE:
        {
            const CAP_PROFILE_CLIENT_UNICAST_CONFIG_COMPLETE_T *config_complete = &ind->body.unicast_config_complete;
            DEBUG_LOG("leAudioClient_ProcessCapUnicastMessageInPtsMode Config Status %d", config_complete->status);
        }
        break;

        case CAP_PROFILE_CLIENT_MSG_ID_UNICAST_CIS_CONNECT:
        {
            const CAP_PROFILE_CLIENT_UNICAST_CIS_CONNECT_T *cis_connect = &ind->body.unicast_cis_connect;
            DEBUG_LOG("leAudioClient_ProcessCapUnicastMessageInPtsMode Cis Status %d", cis_connect->status);
        }
        break;

        case CAP_PROFILE_CLIENT_MSG_ID_UNICAST_STREAM_START:
        {
            const CAP_PROFILE_CLIENT_UNICAST_STREAM_START_T *stream_start =  &ind->body.unicast_stream_start;
            DEBUG_LOG("leAudioClient_ProcessCapUnicastMessageInPtsMode Stream Start %d", stream_start->status);
        }
        break;

        case CAP_PROFILE_CLIENT_MSG_ID_UNICAST_STREAM_STOP:
        {
            const CAP_PROFILE_CLIENT_UNICAST_STREAM_STOP_T *stream_stop =  &ind->body.unicast_stream_stop;
           DEBUG_LOG("leAudioClient_ProcessCapUnicastMessageInPtsMode Strea Stop %d", stream_stop->status);
        }
        break;

        default:
        break;
    }
}

bool leAudioClient_IsInUnicastStreaming(void)
{
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    return (leAudioClient_IsStreamingEnabled() && client_ctxt->mode == LE_AUDIO_CLIENT_MODE_UNICAST);
}

/* Process all the CAP profile messages */
void leAudioClient_ProcessCapUnicastMessage(const cap_profile_client_msg_t *message)
{
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    if (client_ctxt->state < LE_AUDIO_CLIENT_STATE_CONNECTED)
    {
        return;
    }

    if (client_ctxt->pts_mode == LE_AUDIO_CLIENT_PTS_MODE_CAP)
    {
        leAudioClient_ProcessCapUnicastMessageInPtsMode(message);
        return;
    }

    switch (message->id)
    {
        case CAP_PROFILE_CLIENT_MSG_ID_UNICAST_CONFIG_COMPLETE:
        {
            const CAP_PROFILE_CLIENT_UNICAST_CONFIG_COMPLETE_T *config_complete = &message->body.unicast_config_complete;

            leAudioClient_StartStreamingIfConfigCompleted(config_complete->group_handle, config_complete->audio_context,
                                                          config_complete->status == CAP_PROFILE_CLIENT_STATUS_SUCCESS);
        }
        break;

        case CAP_PROFILE_CLIENT_MSG_ID_UNICAST_CIS_CONNECT:
        {
            const CAP_PROFILE_CLIENT_UNICAST_CIS_CONNECT_T *cis_connect = &message->body.unicast_cis_connect;

            leAudioClient_HandleUnicastCisConnectInd(cis_connect->cid, cis_connect->cis_count, cis_connect->cis_handles,
                                                     cis_connect->codec_qos_config,
                                                     cis_connect->status == CAP_PROFILE_CLIENT_STATUS_SUCCESS);
        }
        break;

        case CAP_PROFILE_CLIENT_MSG_ID_UNICAST_STREAM_START:
        {
            const CAP_PROFILE_CLIENT_UNICAST_STREAM_START_T *stream_start =  &message->body.unicast_stream_start;

            leAudioClient_HandleUnicastStreamStartInd(stream_start->status == CAP_PROFILE_CLIENT_STATUS_SUCCESS);
        }
        break;

        case CAP_PROFILE_CLIENT_MSG_ID_UNICAST_STREAM_STOP:
        {
            const CAP_PROFILE_CLIENT_UNICAST_STREAM_STOP_T *stream_stop =  &message->body.unicast_stream_stop;

            leAudioClient_HandleUnicastStreamStopInd(stream_stop->group_handle, LE_AUDIO_CLIENT_PROFILE_CAP,
                                                     stream_stop->status == CAP_PROFILE_CLIENT_STATUS_SUCCESS);
        }
        break;

        case CAP_PROFILE_CLIENT_MSG_ID_CIS_LINK_LOSS:
            leAudioClient_HandleCisLinkLossInd(&message->body.cis_link_loss);
        break;

        case CAP_PROFILE_CLIENT_MSD_ID_VOLUME_STATE:
        {
            leAudioClient_HandleUnicastVolumeStateInd(message->body.volume_state.volumeState);
        }
        break;

        case CAP_PROFILE_CLIENT_MSD_ID_UNICAST_CONFIG_REMOVED:
        {
            const CAP_PROFILE_CLIENT_UNICAST_CONFIG_REMOVED_T *config_removed =  &message->body.unicast_config_removed;

            leAudioClient_HandleUnicastConfigRemovedInd(config_removed->group_handle,
                                                        config_removed->status == CAP_PROFILE_CLIENT_STATUS_SUCCESS);
        }
        break;

        case CAP_PROFILE_CLIENT_MSD_ID_FLUSH_TIMEOUT_INFO:
        {
            const CAP_PROFILE_CLIENT_MSD_ID_FLUSH_TIMEOUT_INFO_T *flush_timeout_info =  &message->body.flush_timeout_info;

            leAudioClient_UpdateFlushTimeoutRange(flush_timeout_info->group_handle, (cap_profile_ft_info_t*)&flush_timeout_info->ft_info);
        }
        break;

        default:
        break;
    }
}

static void leAudioClient_ProcessTmapUnicastMessageInPtsMode(const tmap_client_unicast_msg_t *ind)
{
    switch (ind->id)
    {
        case TMAP_CLIENT_MSG_ID_UNICAST_CONFIG_COMPLETE:
        {
            const TMAP_CLIENT_MSG_ID_UNICAST_CONFIG_COMPLETE_T *config_complete = &ind->body.unicast_config_complete;
            DEBUG_LOG("leAudioClient_ProcessTmapUnicastMessageInPtsMode Config Status 0x%x", config_complete->status);
        }
        break;

        case TMAP_CLIENT_MSG_ID_UNICAST_CIS_CONNECT:
        {
            const TMAP_CLIENT_MSG_ID_UNICAST_CIS_CONNECT_T *cis_connect = &ind->body.unicast_cis_connect;
            DEBUG_LOG("leAudioClient_ProcessTmapUnicastMessageInPtsMode CIS Status %d", cis_connect->status);
        }
        break;

        case TMAP_CLIENT_MSG_ID_UNICAST_STREAM_START:
        {
            const TMAP_CLIENT_MSG_ID_UNICAST_STREAM_START_T *stream_start =  &ind->body.unicast_stream_start;
            DEBUG_LOG("leAudioClient_ProcessTmapUnicastMessageInPtsMode Stream Start %d", stream_start->status);

            LeAudioClient_StartPtsStreamingToUnicastDevices(TmapClientSource_PtsGetFirstGattDeviceCid(),
                                                            TmapClientSource_PtsGetSecondGattDeviceCid(),
                                                            TmapClientSource_PtsIsSpkrPresent(),
                                                            TmapClientSource_PtsIsMicPresent());
         }
        break;

        case TMAP_CLIENT_MSG_ID_UNICAST_STREAM_STOP:
        {
            const TMAP_CLIENT_MSG_ID_UNICAST_STREAM_STOP_T *stream_stop =  &ind->body.unicast_stream_stop;
            DEBUG_LOG("leAudioClient_ProcessTmapUnicastMessageInPtsMode Stream Stopt %d", stream_stop->status);
        }
        break;

        default:
        break;
    }
}

/* Handle TMAP Profile unicast messages in connecting state */
static void leAudioClient_HandleTmapUnicastMessagesInConnectingState(const tmap_client_unicast_msg_t *message)
{
    switch (message->id)
    {
        case TMAP_CLIENT_MSG_ID_UNICAST_CONFIG_COMPLETE:
        {
            const TMAP_CLIENT_MSG_ID_UNICAST_CONFIG_COMPLETE_T *cfm = &message->body.unicast_config_complete;

            leAudioClient_HandleConfigComplete(cfm->group_handle, cfm->audio_context, cfm->status == TMAP_CLIENT_MSG_STATUS_SUCCESS);
        }
        break;

        default:
        break;
    }
}

/* Handle TMAP Profile unicast messages in disconnecting state */
static void leAudioClient_HandleTmapUnicastMessagesInDisconnectingState(const tmap_client_unicast_msg_t *message)
{
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    switch (message->id)
    {
        case TMAP_CLIENT_MSG_ID_UNICAST_STREAM_STOP:
        {
            const TMAP_CLIENT_MSG_ID_UNICAST_STREAM_STOP_T *cfm = &message->body.unicast_stream_stop;

            DEBUG_LOG("leAudioClient_HandleTmapMessagesInDisconnectingState: TMAP_CLIENT_MSG_ID_UNICAST_STREAM_STOP_T Status %d", cfm->status);

            if (client_ctxt->session_data.release_config)
            {
                PanicFalse(TmapClientSource_StopUnicastStreaming(client_ctxt->group_handle, TRUE));
                client_ctxt->session_data.release_config = FALSE;
            }
            else
            {
                /* Destroy the instance of given cid alone */
                PanicFalse(TmapClientSource_DestroyInstance(client_ctxt->group_handle, client_ctxt->gatt_cid));
            }
        }
        break;

        default:
        break;
    }
}

/* Process all the TMAP profile unicast messages */
static void leAudioClient_ProcessTmapUnicastMessage(const tmap_client_unicast_msg_t *message)
{
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    if (client_ctxt->state < LE_AUDIO_CLIENT_STATE_CONNECTED)
    {
        return;
    }

    /* If in PTS mode just by pass all the TMAP messages to the TMAP PTS message handler */
    if (client_ctxt->pts_mode != LE_AUDIO_CLIENT_PTS_MODE_OFF)
    {
        leAudioClient_ProcessTmapUnicastMessageInPtsMode(message);
        return;
    }

    switch (message->id)
    {
        case TMAP_CLIENT_MSG_ID_UNICAST_CONFIG_COMPLETE:
        {
            const TMAP_CLIENT_MSG_ID_UNICAST_CONFIG_COMPLETE_T *config_complete = &message->body.unicast_config_complete;

            leAudioClient_StartStreamingIfConfigCompleted(config_complete->group_handle, config_complete->audio_context,
                                                          config_complete->status == TMAP_CLIENT_MSG_STATUS_SUCCESS);
        }
        break;

        case TMAP_CLIENT_MSG_ID_UNICAST_CIS_CONNECT:
        {
            const TMAP_CLIENT_MSG_ID_UNICAST_CIS_CONNECT_T *cis_connect = &message->body.unicast_cis_connect;

            leAudioClient_HandleUnicastCisConnectInd(cis_connect->cid, cis_connect->cis_count, cis_connect->cis_handles,
                                                     cis_connect->codec_qos_config,
                                                     cis_connect->status == TMAP_CLIENT_MSG_STATUS_SUCCESS);
        }
        break;

        case TMAP_CLIENT_MSG_ID_UNICAST_STREAM_START:
        {
            const TMAP_CLIENT_MSG_ID_UNICAST_STREAM_START_T *stream_start =  &message->body.unicast_stream_start;

            leAudioClient_HandleUnicastStreamStartInd(stream_start->status == TMAP_CLIENT_MSG_STATUS_SUCCESS);
        }
        break;

        case TMAP_CLIENT_MSG_ID_UNICAST_STREAM_STOP:
        {
            const TMAP_CLIENT_MSG_ID_UNICAST_STREAM_STOP_T *stream_stop =  &message->body.unicast_stream_stop;

            leAudioClient_HandleUnicastStreamStopInd(stream_stop->group_handle, LE_AUDIO_CLIENT_PROFILE_TMAP,
                                                     stream_stop->status == TMAP_CLIENT_MSG_STATUS_SUCCESS);
        }
        break;

        case TMAP_CLIENT_MSG_ID_UNICAST_CONFIG_REMOVED:
        {
            const TMAP_CLIENT_MSG_ID_UNICAST_CONFIG_REMOVED_T *config_removed =  &message->body.unicast_config_removed;

            leAudioClient_HandleUnicastConfigRemovedInd(config_removed->group_handle,
                                                        config_removed->status == TMAP_CLIENT_MSG_STATUS_SUCCESS);
        }
        break;

        default:
        break;
    }
}

/*! \brief Process TMAP Domain unicast Messages */
void leAudioClient_ProcessTmapClientUnicastMessage(const tmap_client_unicast_msg_t *message)
{
    switch (LeAudioClient_GetState())
    {
        case LE_AUDIO_CLIENT_STATE_CONNECTING:
            leAudioClient_HandleTmapUnicastMessagesInConnectingState(message);
        break;

        case LE_AUDIO_CLIENT_STATE_DISCONNECTING:
            leAudioClient_HandleTmapUnicastMessagesInDisconnectingState(message);
        break;

        case LE_AUDIO_CLIENT_STATE_CONNECTED:
            leAudioClient_ProcessTmapUnicastMessage(message);
        break;

        default:
        break;
    }
}

bool leAudioClient_StartUnicastStreaming(ServiceHandle group_handle, uint16 audio_context)
{
    bool stream_start_req = FALSE;
    le_audio_client_profiles_t profile;
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    if (client_ctxt->state < LE_AUDIO_CLIENT_STATE_CONNECTED ||
        leAudioClient_IsInUnicastStreaming())
    {
        DEBUG_LOG("leAudioClient_StartUnicastStreaming Failed State : %d", client_ctxt->state);
        return FALSE;
    }

    DEBUG_LOG("leAudioClient_StartUnicastStreaming audio_context:0x%x", audio_context);

    profile = leAudioClient_GetProfileForAudioContext(audio_context);

    if (!client_ctxt->pts_mode)
    {
        leAudioClient_StartUnicastAudioSession(group_handle, audio_context);

        /* If the requested audio context is already configured, start streaming it directly */
        if (client_ctxt->configured_audio_contexts & audio_context)
        {
            if (profile == LE_AUDIO_CLIENT_PROFILE_CAP)
            {
                leAudioClient_SetDefaultFlushTimeoutRange(group_handle, audio_context);
                stream_start_req = CapProfileClient_StartUnicastStreaming(group_handle, audio_context);
            }
            else
            {
                stream_start_req = TmapClientSource_StartUnicastStreaming(group_handle, audio_context);
            }
        }
        else
        {
            /* Requested audio context is not configured, configure it */
            stream_start_req = leAudioClient_ConfigureUnicastStreaming(group_handle, audio_context);
        }
    }

    return stream_start_req;
}

bool leAudioClient_StopUnicastStreaming(ServiceHandle group_handle, bool remove_config)
{
    bool stop_streaming = FALSE;
    le_audio_client_profiles_t profile_streaming;
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();

    if (leAudioClient_IsInUnicastStreaming())
    {
        profile_streaming = leAudioClient_GetProfileForAudioContext(client_ctxt->session_data.audio_context);

        stop_streaming = profile_streaming == LE_AUDIO_CLIENT_PROFILE_TMAP ? TmapClientSource_StopUnicastStreaming(group_handle, FALSE) :
                                                                             CapProfileClient_StopUnicastStreaming(group_handle, FALSE);

        if (stop_streaming)
        {
            DEBUG_LOG("LeAudioClient_StopStreaming profile:%d group_handle:0x%x", profile_streaming, group_handle);
            if (remove_config)
            {
                client_ctxt->configured_audio_contexts &= (~client_ctxt->session_data.audio_context);
            }

            /* Remove the config later once the streaming has stopped and all the underlying
             * ASEs are disabled and CIS is disconnected by CAP.
             */
            client_ctxt->session_data.release_config = remove_config;
        }
    }

    return stop_streaming;
}

bool leAudioClient_ConfigureUnicastStreaming(ServiceHandle group_handle, CapClientContext audio_context)
{
    bool config_req_sent = FALSE;
    le_audio_client_profiles_t profile;
    const le_audio_client_audio_config_t *audio_config;
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();
    CapClientQhsConfig qhs_config;
    uint32 sink_stream_capability, src_stream_capability;

    if (client_ctxt->pts_mode != LE_AUDIO_CLIENT_PTS_MODE_OFF)
    {
        /* In PTS mode we should only allow configuration using PTS test APIs */
        return FALSE;
    }

    DEBUG_LOG("leAudioClient_ConfigureUnicastStreaming audio_context:0x%x, QHS_Enabled:%d",
               audio_context,
               client_ctxt->iso_qhs_supported);

    if(client_ctxt->pts_mode == LE_AUDIO_CLIENT_PTS_MODE_CAP ||
       client_ctxt->pts_mode == LE_AUDIO_CLIENT_PTS_MODE_TMAP)
    {
        return FALSE;
    }

    profile = leAudioClient_GetProfileForAudioContext(audio_context);

    if (leAudioClient_CanAptxLiteCodecBeUsed(audio_context, group_handle))
    {
        audio_config = leAudioClient_GetAudioConfig(audio_context, KYMERA_LE_AUDIO_CODEC_APTX_LITE);
        sink_stream_capability = audio_config->sink_stream_capability;
        src_stream_capability = audio_config->source_stream_capability;
    }
    else
    {
        audio_config = leAudioClient_GetAudioConfig(audio_context, KYMERA_LE_AUDIO_CODEC_LC3);
        sink_stream_capability = leAudioClient_GetStreamCapability(audio_config->sink_stream_capability);
        src_stream_capability = leAudioClient_GetStreamCapability(audio_config->source_stream_capability);
    }

    PanicNull((void *) audio_config);

    if (client_ctxt->iso_qhs_supported)
    {
        qhs_config.framing = audio_config->framing;
        qhs_config.phyCtoP = audio_config->phy_ctop;
        qhs_config.phyPtoC = audio_config->phy_ptoc;
        qhs_config.rtnCtoP = audio_config->rtn_ctop;
        qhs_config.rtnPtoC = audio_config->rtn_ptoc;
    }

    if (profile == LE_AUDIO_CLIENT_PROFILE_CAP)
    {
        config_req_sent = CapProfileClient_ConfigureForGaming(group_handle,
                                                     sink_stream_capability,
                                                     src_stream_capability,
                                                     audio_config->target_latency,
                                                     leAudioClient_IsMicrophoneNeededForContext(audio_context) ?
                                                     LE_AUDIO_CLIENT_CLIENT_MICROPHONE_COUNT : CAP_PROFILE_CLIENT_MICROPHONE_COUNT_NONE,
                                                     client_ctxt->iso_qhs_supported ? LE_AUDIO_CLIENT_CONFIG_MODE_QHS : LE_AUDIO_CLIENT_DEFAULT_CONFIG_MODE,
                                                     client_ctxt->iso_qhs_supported ? &qhs_config : NULL);

    }
    else if (profile == LE_AUDIO_CLIENT_PROFILE_TMAP)
    {
        config_req_sent = TmapClientSource_Configure(group_handle,
                                               leAudioClient_GetStreamCapability(audio_config->sink_stream_capability),
                                               leAudioClient_GetStreamCapability(audio_config->source_stream_capability),
                                               leAudioClient_IsMicrophoneNeededForContext(audio_context) ?
                                               LE_AUDIO_CLIENT_CLIENT_MICROPHONE_COUNT : CAP_PROFILE_CLIENT_MICROPHONE_COUNT_NONE,
                                               client_ctxt->iso_qhs_supported ? LE_AUDIO_CLIENT_CONFIG_MODE_QHS : LE_AUDIO_CLIENT_DEFAULT_CONFIG_MODE,
                                               client_ctxt->iso_qhs_supported ? &qhs_config : NULL);
    }

    if (config_req_sent)
    {
        client_ctxt->requested_audio_contexts |= audio_context;
    }

    return config_req_sent;
}

/* A unicast audio session is starting. Preserve the audio parameters used for the session */
void leAudioClient_StartUnicastAudioSession(ServiceHandle group_handle, CapClientContext audio_context)
{
    le_audio_client_context_t *client_ctxt = leAudioClient_GetContext();
    uint8 codec = KYMERA_LE_AUDIO_CODEC_LC3;

    DEBUG_LOG("leAudioClient_StartUnicastAudioSession");

    if (leAudioClient_CanAptxLiteCodecBeUsed(audio_context, group_handle))
    {
        codec = KYMERA_LE_AUDIO_CODEC_APTX_LITE;
    }

    client_ctxt->session_data.audio_context = audio_context;
    client_ctxt->session_data.audio_config = leAudioClient_GetAudioConfig(audio_context, codec);
    PanicNull((void *) client_ctxt->session_data.audio_config);
}

/* A unicast audio session is ending. Clean the audio parameters used for the session */
void leAudioClient_EndUnicastAudioSession(le_audio_unicast_session_data_t *session_data)
{
    uint8 cid_index;

    DEBUG_LOG("leAudioClient_EndUnicastAudioSession");
    memset(session_data, 0, sizeof(le_audio_unicast_session_data_t));

    for (cid_index = 0; cid_index < LE_AUDIO_CLIENT_MAX_DEVICES_SUPPORTED ; cid_index++)
    {
        session_data->devices_cis_info[cid_index].cid = INVALID_CID;
    }
}

le_audio_client_profiles_t leAudioClient_GetProfileForAudioContext(CapClientContext audio_context)
{
    le_audio_client_profiles_t profile_to_use = LE_AUDIO_CLIENT_PROFILE_NONE;

    switch (audio_context)
    {
       case CAP_CLIENT_CONTEXT_TYPE_GAME_WITH_VBC:
       case CAP_CLIENT_CONTEXT_TYPE_GAME:
           profile_to_use = LE_AUDIO_CLIENT_PROFILE_CAP;
       break;

       case CAP_CLIENT_CONTEXT_TYPE_MEDIA:
       case CAP_CLIENT_CONTEXT_TYPE_CONVERSATIONAL:
           profile_to_use = LE_AUDIO_CLIENT_PROFILE_TMAP ;
       break;

       default:
       break;
    }

    /* Panic if no matching profiles found for given context */
    PanicFalse(profile_to_use != LE_AUDIO_CLIENT_PROFILE_NONE);

    DEBUG_LOG("leAudioClient_GetProfileForAudioContext enum:le_audio_client_profiles_t:%d", profile_to_use);

    return profile_to_use;
}

/*! From the requested contexts bitmasks, find the next audio context to configure. This is done by taking the
    rightmost bit (audio context) set in the requested audio contexts.
*/
CapClientContext leAudioClient_GetNextAudioContextToConfigure(CapClientContext requested_audio_contexts)
{
    CapClientContext next_audio_context = 1;

    if (requested_audio_contexts == 0)
    {
        DEBUG_LOG("leAudioClient_GetNextAudioContextToConfigure no pending context to configure");

        /* There is no bits set in given bitmask, so no context to configure */
        return CAP_CLIENT_CONTEXT_TYPE_PROHIBITED;
    }

    /* Get the rightmost bit set in the requested_audio_contexts */
    while ((requested_audio_contexts & next_audio_context) == 0)
    {
        next_audio_context = next_audio_context << 1;
    }

    DEBUG_LOG("leAudioClient_GetNextAudioContextToConfigure requested_audio_contexts: 0x%x audio_context_to_configure: 0x%x",
               requested_audio_contexts, next_audio_context);

    return next_audio_context;
}

static uint16 leAudioClient_GetCisHandleForAudioLocation(uint16 audio_location, uint8 direction)
{
    le_audio_client_cis_devices_info_t *dev, *end_dev;
    le_audio_client_cis_info_t *cis, *end_cis;

    for (dev = &leAudioClient_GetContext()->session_data.devices_cis_info[0],
            end_dev = &dev[LE_AUDIO_CLIENT_MAX_DEVICES_SUPPORTED - 1]; dev <= end_dev; dev++)
    {
        if (dev->cid != INVALID_CID)
        {
            for (cis = &dev->cis_info[0], end_cis = &dev->cis_info[dev->cis_count]; cis < end_cis; cis++)
            {
                if (cis->audio_location == audio_location &&
                    (cis->direction == LE_CIS_DIRECTION_BOTH || cis->direction == direction))
                {
                    DEBUG_LOG_INFO("leAudioClient_GetCisHandleForAudioLocation Handle 0x%x for location 0x%x, direction %d",
                                   cis->cis_handle, audio_location, cis->direction);
                    return cis->cis_handle;
                }
            }
        }
    }

    return INVALID_CIS_HANDLE;
}

uint16 leAudioClient_GetSinkCisHandleForAudioLocation(uint16 audio_location)
{
    return leAudioClient_GetCisHandleForAudioLocation(audio_location, LE_CIS_DIRECTION_DL);
}

uint16 leAudioClient_GetSrcCisHandleForAudioLocation(uint16 audio_location)
{
    return leAudioClient_GetCisHandleForAudioLocation(audio_location, LE_CIS_DIRECTION_UL);
}

uint16 leAudioClient_GetSampleRate(uint16 cap_stream_capability)
{
    uint32 sample_rate = 0;

    switch(cap_stream_capability)
    {
        case CAP_CLIENT_STREAM_CAPABILITY_16_1:
        case CAP_CLIENT_STREAM_CAPABILITY_16_2:
            sample_rate = 16000;
            break;

        case CAP_CLIENT_STREAM_CAPABILITY_48_1:
        case CAP_CLIENT_STREAM_CAPABILITY_48_2:
        case CAP_CLIENT_STREAM_CAPABILITY_48_3:
        case CAP_CLIENT_STREAM_CAPABILITY_48_4:
        case CAP_CLIENT_STREAM_CAPABILITY_48_5:
        case CAP_CLIENT_STREAM_CAPABILITY_48_6:
            sample_rate = 48000;
            break;

        case CAP_CLIENT_STREAM_CAPABILITY_32_1:
        case CAP_CLIENT_STREAM_CAPABILITY_32_2:
            sample_rate = 32000;
            break;

        default:
            DEBUG_LOG_INFO("leAudioClient_GetSampleRate Not Found");
            break;
    }

    return sample_rate;
}

bool LeAudioClient_CreateIncomingCall(void)
{
    return LeTmapServer_CreateCall(TBS_CALL_STATE_INCOMING, TBS_CALL_FLAG_CALL_DIRECTION_INCOMING);
}

bool LeAudioClient_CreateActiveOutgoingCall(void)
{
   return LeTmapServer_CreateCall(TBS_CALL_STATE_ACTIVE, TBS_CALL_FLAG_CALL_DIRECTION_OUTGOING);
}

bool LeAudioClient_IsCallActive(void)
{
    return LeTmapServer_IsCallPresent();
}

uint8 LeAudioClient_GetCurrentMediaState(void)
{
    return  LeTmapServer_GetCurrentMediaState();
}

#endif /* INCLUDE_LE_AUDIO_UNICAST_SOURCE */
